#!/usr/bin/env python2

import sys
import errno
import getopt
import re

from config import Config
from utils import Exp, _dwarn, _exec_pipe, _get_value, _human_unreadable

cache_status = {"none", "ready", "need rebuild", "need reboot"}


"""
lvm table

root@jack_eos:/home/gray/lich/admin# dmsetup table
vg0-disk1: 0 1048576 linear 8:16 8388992
vg0-disk0: 0 8388608 linear 8:16 384
"""

"""
flash cache table
root@jack_eos:/home/gray/lich/admin# dmsetup table
cache_dev: 0 134217728 flashcache conf:
	ssd dev (/dev/sdb), disk dev (/dev/sdc) cache mode(WRITE_BACK)
	capacity(10198M), associativity(512), data block size(4K) metadata block size(4096b)
	disk assoc(256K)
	skip sequential thresh(0K)
	total blocks(2610688), cached blocks(30759), cache percent(1)
	dirty blocks(0), dirty percent(0)
	nr_queued(0)
Size Hist: 4096:31142 
"""

def get_size(dev, s):
    p = re.findall(r'^Disk %s:[^,]+, \d+ bytes' % (dev), s, re.M)
    array = p[0].split(' ')
    return array[-2]

def get_partition(dev, s):
    p = re.findall(r'^%s\d+.+$\n' % (dev), s, re.M)
    for i in p:
        array = i.split(' ')
        print(array)

class SSD:
    def __init__(self, config, name):
        self.config = config
        self.name = name
        s = _exec_pipe(["fdisk", "-l", dev], 0, False)

class Cache:
    def __init__(self, config):
        self.config = config

    def __getpv(self):
        s = _exec_pipe(["pvdisplay"], 0, False)
        lst = re.findall(r'PV.+Name.+', s, re.M)
        disk = []
        for i in lst:
            p = i.split(" ")
            disk.append(p[-1])

        return disk

    def __get_system_part(self):
        d = {}
        s = _exec_pipe(["mount"], 0, False)
        lst = re.findall(r'^/dev/sd.+', s, re.M)
        for i in lst:
            p = i.split(" ")
            d[p[0]] = p[2]

        s = _exec_pipe(["blkid"], 0, False)
        lst = re.findall(r'^.+ TYPE="swap"', s, re.M)
        for i in lst:
            p = i.split(" ")
            d[p[0][:-1]] = "swap"

        return d

    def __get_system_disk(self):
        s = set()
        part = self.__get_system_part()
        for (k,v) in part.items():
            disk = re.match("^/dev/sd[^\d]+", k)
            s.add(disk.group())

        return s

    def __getdisk(self):
        pv = self.__getpv()

        """
        sys = self.__get_system_disk()
        #print ("sys:" + str(sys))
        if (len(sys) is not 1):
            print (sys)
            raise Exp(errno.EPERM, "bad config")
        """
        s = _exec_pipe(["fdisk", "-l"], 0, False)
        lst = re.findall(r'^Disk /dev[^:]+', s, re.M)
        disk = []
        for i in lst:
            p = i.split(" ")
            if ("/dev/sd" not in p[1]):
                _dwarn("skip %s" % (p[1]))
                continue;

            if (p[1] in pv):
                _dwarn("skip pv %s" % (p[1]))
                continue;

            """
            if (p[1] in sys):
                _dwarn("skip sys disk %s" % (p[1]))
                continue;
            """

            s1 = _exec_pipe(["fdisk", "-l", p[1]], 0, False)
            disk.append(p[1])
        return disk

    def __get_type(self, dev):
        fake_ssd = None
        fake_hdd = None

        try:
            s = _get_value("%s/fake_ssd" % self.config.shm)[:-1]
            fake_ssd = set(s.split('\n'))
        except OSError as err:
            pass

        try:
            s = _get_value("%s/fake_hdd" % self.config.shm)[:-1]
            fake_hdd = set(s.split('\n'))
        except OSError as err:
            pass

        if (fake_hdd and fake_ssd):
            assert(len(fake_hdd & fake_ssd) == 0)

        if (fake_ssd and (dev in fake_ssd)):
            return "SSD"
        if (fake_hdd and (dev in fake_hdd)):
            return "HDD"

        s = _exec_pipe(["hdparm", "-I", dev], 0, False)
        model = re.findall(r'Model.+Number:.+', s, re.M)
        if (len(model) == 0):
            return "HDD"
            #_derror('skip devices %s' % (dev))
            #return "UNKNOW"
        if ("SSD" in model[0]):
            return "SSD"
        else:
            return "HDD"

    def __get_mount(self):
        s = _exec_pipe(["mount"], 0, False)
        lst = re.findall(r'^/dev/sd[^\t\n]+ on %s/disk/\d+' % (self.config.home), s, re.M)
        m = []
        for i in lst:
            r = i.split(' ')
            m.append(r[0])

        return m

    def __get_free(self, dev):
        s = _exec_pipe(["parted", dev, "print", "free"], 0, False)
        lst = re.findall(r'^.+ Free Space\n', s, re.M)
        res = []
        for i in lst:
            lst1 = re.findall(r'[\d.]+[kMG]B', i, 0)
            res.append((_human_unreadable(lst1[1]), _human_unreadable(lst1[2])))

        return res

    def __getssd(self, disk, used):
        ssd = []
        for i in disk:
            t = self.__get_type(i)
            if (t == "UNKNOW"):
                continue;
            if ("SSD" in t):
                if (i in used):
                    _dwarn("skip used %s" % (i))
                    continue;
                ssd.append(i)
        return ssd

    def __gethdd(self, disk, used):
        mount = self.__get_mount()
        print(mount)
        hdd = []
        for i in disk:
            t = self.__get_type(i)
            if (t == "UNKNOW"):
                continue;
            if ("SSD" not in t and i in mount):
                if (i in used):
                    _dwarn("skip used %s" % (i))
                    continue;
                hdd.append(i)
        return hdd

    def __get_cached(self):
        s = _exec_pipe(["dmsetup", "table"], 0, False)
        if (s == "No devices found\n"):
            return {}, [], []

        names = re.findall(r'^[^:\tS]+', s, re.M)
        lst = re.findall(r'ssd dev \(/dev/sd[^)]+\), disk dev \(/dev/sd[^)]+\)', s, re.M)
        assert(len(names) == len(lst))
        cache = {}
        usedssd = []
        usedhdd = []
        for i in range(len(names)):
            p = re.findall(r'/dev/sd[^)]', lst[i], re.M)
            cache[names[i]] = p
            usedssd.append(p[0])
            usedhdd.append(p[1])
            #cache[p[0]] = p[1]

        return cache, usedssd, usedhdd

    def __check_config(self, cache, ssd, hdd):
        if (len(ssd[0]) + len(ssd[1]) > len(hdd[0]) + len(hdd[1])):
            return "bad config, too many ssd"

        for (k,v) in cache.items():
            if ("mds_disk" not in (k)):
                return "bad config, bad cache name '%s'" % (k)
            
            t = self.__get_type(v[0])
            if (t is not "SSD"):
                return "bad config, '%s' is not SSD" % (v[0])

            t = self.__get_type(v[1])
            if (t is "SSD"):
                return "bad config, '%s' is not HDD" % (v[1])

        if (len(cache) == 0):
            return "none"
        elif (len(ssd) or len(hdd)):
            return "need rebuild"
        else:
            return "ready"

    def __status(self):
        cache, usedssd, usedhdd = self.__get_cached()
        disk = self.__getdisk()
        ssd = self.__getssd(disk, usedssd)
        hdd = self.__gethdd(disk, usedhdd)
        status = self.__check_config(cache, (usedssd, ssd), (usedhdd, hdd))

        return status, cache, disk, ssd, hdd

    def status(self):
        status, cache, disk, ssd, hdd = self.__status()

        for i in ssd:
            self.__get_free(i)

        print("status:%s" % (status))
        print("    disk:" + str(disk))
        print("    cached:" + str(cache))
        print("    uncached ssd:" + str(ssd))
        print("    uncached hdd:" + str(hdd))

    def __get_size1(self, dev):
        s = _exec_pipe(["fdisk", "-l", dev], 0, False)
        p = re.findall(r'^Disk %s:[^,]+, \d+ bytes' % (dev), s, re.M)
        array = p[0].split(' ')
        return array[-2]

    def __fdisk(self):
        status, cache, disk, ssd, hdd = self.__status()
        if (status is not "none"):
            raise Exp(errno.EPERM, "cache status error")

        _ssd = {}
        ssd_total = 0
        for i in ssd:
            size = int(self.__get_size1(i))
            _ssd[i] = size
            ssd_total += size
        print (_ssd)

        _hdd = {}
        hdd_total = 0
        for i in hdd:
            size = int(self.__get_size1(i))
            _hdd[i] = size
            hdd_total += size
        print (_hdd)

        avg = ssd_total / len(hdd)
        print ("avg:" + str(avg / 1024 / 1024 / 1024) + "GB")

    def remove(self):
        pass

    def build(self):
        self.__fdisk()

def usage():
    print ("usage:")
    print (sys.argv[0] + " --status")
    print (sys.argv[0] + " --build")

def main():
    try:
        opts, args = getopt.getopt(
            sys.argv[1:], 
            'hv', ['status', 'build']
            )
    except getopt.GetoptError, err:
        print str(err)
        usage()
        exit(errno.EINVAL)

    config = Config()
    cache = Cache(config)
    for o, a in opts:
        if o in ('--help'):
            usage()
            exit(0)
        elif o in ('-v', '--verbose'):
            verbose = 1
        elif o in ('--json'):
            is_json = 1
        elif o == '--status':
            cache.status()
        elif o == '--build':
            cache.build()

if __name__ == '__main__':
    if (len(sys.argv) == 1):
        usage()
    else:
        main()
